package com.frank.ycj520.customview.commonview.loadingview;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AnimationUtils;
import com.frank.ycj520.customview.R;
import com.frank.ycj520.customview.commonview.loadingview.indicators.BallPulseIndicator;

/**
 * @Author Created by gjson on 2018/12/29.
 */
public class LoadingIndicatorView  extends View {

  private static final String TAG="AVLoadingIndicatorView";

  private static final BallPulseIndicator DEFAULT_INDICATOR=new BallPulseIndicator();

  private static final int MIN_SHOW_TIME = 500; // ms
  private static final int MIN_DELAY = 500; // ms

  private long mStartTime = -1;

  private boolean mPostedHide = false;

  private boolean mPostedShow = false;

  private boolean mDismissed = false;

  private final Runnable mDelayedHide = new Runnable() {

    @Override
    public void run() {
      mPostedHide = false;
      mStartTime = -1;
      setVisibility(View.GONE);
    }
  };

  private final Runnable mDelayedShow = new Runnable() {

    @Override
    public void run() {
      mPostedShow = false;
      if (!mDismissed) {
        mStartTime = System.currentTimeMillis();
        setVisibility(View.VISIBLE);
      }
    }
  };

  int mMinWidth;
  int mMaxWidth;
  int mMinHeight;
  int mMaxHeight;

  private Indicator mIndicator;
  private int mIndicatorColor;

  private boolean mShouldStartAnimationDrawable;

  public LoadingIndicatorView(Context context) {
    super(context);
    init(context, null,0,0);
  }

  public LoadingIndicatorView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs,0,R.style.LoadingIndicatorView);
  }

  public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context, attrs,defStyleAttr,R.style.LoadingIndicatorView);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(context,attrs,defStyleAttr,R.style.LoadingIndicatorView);
  }

  private void init(Context context,AttributeSet attrs,int defStyleAttr, int defStyleRes) {
    mMinWidth = 24;
    mMaxWidth = 48;
    mMinHeight = 24;
    mMaxHeight = 48;

    final TypedArray a = context.obtainStyledAttributes(
        attrs, R.styleable.LoadingIndicatorView, defStyleAttr, defStyleRes);

    mMinWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minWidth, mMinWidth);
    mMaxWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxWidth, mMaxWidth);
    mMinHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minHeight, mMinHeight);
    mMaxHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxHeight, mMaxHeight);
    String indicatorName=a.getString(R.styleable.LoadingIndicatorView_indicatorName);
    mIndicatorColor=a.getColor(R.styleable.LoadingIndicatorView_indicatorColor, Color.WHITE);
    setIndicator(indicatorName);
    if (mIndicator==null){
      setIndicator(DEFAULT_INDICATOR);
    }
    a.recycle();
  }

  public Indicator getIndicator() {
    return mIndicator;
  }

  public void setIndicator(Indicator d) {
    if (mIndicator != d) {
      if (mIndicator != null) {
        mIndicator.setCallback(null);
        unscheduleDrawable(mIndicator);
      }

      mIndicator = d;
      //need to set indicator color again if you didn't specified when you update the indicator .
      setIndicatorColor(mIndicatorColor);
      if (d != null) {
        d.setCallback(this);
      }
      postInvalidate();
    }
  }


  /**
   * setIndicatorColor(0xFF00FF00)
   * or
   * setIndicatorColor(Color.BLUE)
   * or
   * setIndicatorColor(Color.parseColor("#FF4081"))
   * or
   * setIndicatorColor(0xFF00FF00)
   * or
   * setIndicatorColor(getResources().getColor(android.R.color.black))
   * @param color
   */
  public void setIndicatorColor(int color){
    this.mIndicatorColor=color;
    mIndicator.setColor(color);
  }


  /**
   * You should pay attention to pass this parameter with two way:
   * for example:
   * 1. Only class Name,like "SimpleIndicator".(This way would use default package name with
   * "com.wang.avi.indicators")
   * 2. Class name with full package,like "com.my.android.indicators.SimpleIndicator".
   * @param indicatorName the class must be extend Indicator .
   */
  public void setIndicator(String indicatorName){
    if (TextUtils.isEmpty(indicatorName)){
      return;
    }
    StringBuilder drawableClassName=new StringBuilder();
    if (!indicatorName.contains(".")){
      String defaultPackageName=getClass().getPackage().getName();
      drawableClassName.append(defaultPackageName)
          .append(".indicators")
          .append(".");
    }
    drawableClassName.append(indicatorName);
    try {
      Class<?> drawableClass = Class.forName(drawableClassName.toString());
      Indicator indicator = (Indicator) drawableClass.newInstance();
      setIndicator(indicator);
    } catch (ClassNotFoundException e) {
      Log.e(TAG,"Didn't find your class , check the name again !");
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
  }

  public void smoothToShow(){
    startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_in));
    setVisibility(VISIBLE);
  }

  public void smoothToHide(){
    startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_out));
    setVisibility(GONE);
  }

  public void hide() {
    mDismissed = true;
    removeCallbacks(mDelayedShow);
    long diff = System.currentTimeMillis() - mStartTime;
    if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
      // The progress spinner has been shown long enough
      // OR was not shown yet. If it wasn't shown yet,
      // it will just never be shown.
      setVisibility(View.GONE);
    } else {
      // The progress spinner is shown, but not long enough,
      // so put a delayed message in to hide it when its been
      // shown long enough.
      if (!mPostedHide) {
        postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
        mPostedHide = true;
      }
    }
  }

  public void show() {
    // Reset the start time.
    mStartTime = -1;
    mDismissed = false;
    removeCallbacks(mDelayedHide);
    if (!mPostedShow) {
      postDelayed(mDelayedShow, MIN_DELAY);
      mPostedShow = true;
    }
  }

  @Override
  protected boolean verifyDrawable(Drawable who) {
    return who == mIndicator
        || super.verifyDrawable(who);
  }

  void startAnimation() {
    if (getVisibility() != VISIBLE) {
      return;
    }

    if (mIndicator instanceof Animatable) {
      mShouldStartAnimationDrawable = true;
    }
    postInvalidate();
  }

  void stopAnimation() {
    if (mIndicator instanceof Animatable) {
      mIndicator.stop();
      mShouldStartAnimationDrawable = false;
    }
    postInvalidate();
  }

  @Override
  public void setVisibility(int v) {
    if (getVisibility() != v) {
      super.setVisibility(v);
      if (v == GONE || v == INVISIBLE) {
        stopAnimation();
      } else {
        startAnimation();
      }
    }
  }

  @Override
  protected void onVisibilityChanged(View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
    if (visibility == GONE || visibility == INVISIBLE) {
      stopAnimation();
    } else {
      startAnimation();
    }
  }

  @Override
  public void invalidateDrawable(Drawable dr) {
    if (verifyDrawable(dr)) {
      final Rect dirty = dr.getBounds();
      final int scrollX = getScrollX() + getPaddingLeft();
      final int scrollY = getScrollY() + getPaddingTop();

      invalidate(dirty.left + scrollX, dirty.top + scrollY,
          dirty.right + scrollX, dirty.bottom + scrollY);
    } else {
      super.invalidateDrawable(dr);
    }
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    updateDrawableBounds(w, h);
  }

  private void updateDrawableBounds(int w, int h) {
    // onDraw will translate the canvas so we draw starting at 0,0.
    // Subtract out padding for the purposes of the calculations below.
    w -= getPaddingRight() + getPaddingLeft();
    h -= getPaddingTop() + getPaddingBottom();

    int right = w;
    int bottom = h;
    int top = 0;
    int left = 0;

    if (mIndicator != null) {
      // Maintain aspect ratio. Certain kinds of animated drawables
      // get very confused otherwise.
      final int intrinsicWidth = mIndicator.getIntrinsicWidth();
      final int intrinsicHeight = mIndicator.getIntrinsicHeight();
      final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
      final float boundAspect = (float) w / h;
      if (intrinsicAspect != boundAspect) {
        if (boundAspect > intrinsicAspect) {
          // New width is larger. Make it smaller to match height.
          final int width = (int) (h * intrinsicAspect);
          left = (w - width) / 2;
          right = left + width;
        } else {
          // New height is larger. Make it smaller to match width.
          final int height = (int) (w * (1 / intrinsicAspect));
          top = (h - height) / 2;
          bottom = top + height;
        }
      }
      mIndicator.setBounds(left, top, right, bottom);
    }
  }

  @Override
  protected synchronized void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawTrack(canvas);
  }

  void drawTrack(Canvas canvas) {
    final Drawable d = mIndicator;
    if (d != null) {
      // Translate canvas so a indeterminate circular progress bar with padding
      // rotates properly in its animation
      final int saveCount = canvas.save();

      canvas.translate(getPaddingLeft(), getPaddingTop());

      d.draw(canvas);
      canvas.restoreToCount(saveCount);

      if (mShouldStartAnimationDrawable && d instanceof Animatable) {
        ((Animatable) d).start();
        mShouldStartAnimationDrawable = false;
      }
    }
  }

  @Override
  protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int dw = 0;
    int dh = 0;

    final Drawable d = mIndicator;
    if (d != null) {
      dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
      dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
    }

    updateDrawableState();

    dw += getPaddingLeft() + getPaddingRight();
    dh += getPaddingTop() + getPaddingBottom();

    final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
    final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
    setMeasuredDimension(measuredWidth, measuredHeight);
  }

  @Override
  protected void drawableStateChanged() {
    super.drawableStateChanged();
    updateDrawableState();
  }

  private void updateDrawableState() {
    final int[] state = getDrawableState();
    if (mIndicator != null && mIndicator.isStateful()) {
      mIndicator.setState(state);
    }
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Override
  public void drawableHotspotChanged(float x, float y) {
    super.drawableHotspotChanged(x, y);

    if (mIndicator != null) {
      mIndicator.setHotspot(x, y);
    }
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    startAnimation();
    removeCallbacks();
  }

  @Override
  protected void onDetachedFromWindow() {
    stopAnimation();
    // This should come after stopAnimation(), otherwise an invalidate message remains in the
    // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
    super.onDetachedFromWindow();
    removeCallbacks();
  }

  private void removeCallbacks() {
    removeCallbacks(mDelayedHide);
    removeCallbacks(mDelayedShow);
  }


}
